using UnityEngine;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

namespace Hont.UMotionGraph
{
    public class MGPlexusLikeContainer : MonoBehaviour, IMoGraphContainer
    {
        public enum GeometryTypeEnum { Primitive }
        public enum RenderTypeEnum { Point, Line, PointAndLine }

        [Serializable]
        public class GeometrySettings
        {
            [Serializable]
            public class PrimitiveSettings
            {
                public int length = 3;
                public int width = 3;
                public int height = 3;

                public int xPoint = 3;
                public int yPoint = 3;
                public int zPoint = 3;

                public float scale = 1;
            }

            public GeometryTypeEnum geometryType;
            public PrimitiveSettings primitiveSettings;
        }

        [Serializable]
        public class RendererSettings
        {
            public RenderTypeEnum renderType;
            public Material pointMaterial;
            public Material lineMaterial;
            public bool useSharedMaterial;
            public float lineRendererWidth = 0.01f;
        }

        public GeometrySettings geometrySettings;
        public RendererSettings rendererSettings;
        public MoGraphEffector[] effectors;

        List<Transform> mPointsList;

        protected LineRendererHub mLineRendererHub;
        protected Vector3[] mLineRendererPointArray;

        protected List<IMoGraphEffector> mEffectorList;
        protected Vector3[] mUpdateMiddleValueArray;
        protected Vector3[] mSourceValueArray;

        List<IMoGraphEffector> IMoGraphContainer.EffectorList { get { return mEffectorList; } }

        public virtual int Count { get { return mPointsList.Count; } }


        protected virtual void Awake()
        {
            mPointsList = new List<Transform>();
            mEffectorList = new List<IMoGraphEffector>();
            mEffectorList.AddRange(effectors);

            InitGeometry();
            InitRenderType(rendererSettings.renderType);

            mUpdateMiddleValueArray = new Vector3[mPointsList.Count];
            mSourceValueArray = new Vector3[mPointsList.Count];
            for (int i = 0; i < mSourceValueArray.Length; i++)
            {
                mSourceValueArray[i] = mPointsList[i].position;
            }

            mEffectorList.ForEach(m => m.Init(this));
        }

        protected virtual void Update()
        {
            if (Camera.main != null)
            {
                for (int i = 0; i < mPointsList.Count; i++)
                {
                    var item = mPointsList[i];

                    item.transform.LookAt(Camera.main.transform.position);
                    item.transform.forward = -item.transform.forward;
                }
            }

            for (int i = 0; i < mUpdateMiddleValueArray.Length; i++)
            {
                mUpdateMiddleValueArray[i] = GetValue(0, i);
            }

            for (int i = 0; i < mEffectorList.Count; i++)
            {
                var item = mEffectorList[i];

                for (int j = 0; j < mUpdateMiddleValueArray.Length; j++)
                {
                    mUpdateMiddleValueArray[j] = item.Execute(j, mUpdateMiddleValueArray[j]);
                }
            }

            UpdateValue(mUpdateMiddleValueArray);

            TryUpdateLineRenderer();
        }

        protected virtual void OnDrawGizmos()
        {
            #region --- Draw Geometry Gizmos ---
            if (Application.isPlaying)
            {
                for (int i = 0; i < mPointsList.Count; i++)
                {
                    Gizmos.DrawWireSphere(mPointsList[i].transform.position, 0.05f);
                }
            }
            else
            {
                switch (geometrySettings.geometryType)
                {
                    case GeometryTypeEnum.Primitive:

                        var primitive = geometrySettings.primitiveSettings;

                        var original = transform.position;
                        float xStep = primitive.length / (float)primitive.xPoint;
                        float yStep = primitive.height / (float)primitive.yPoint;
                        float zStep = primitive.width / (float)primitive.zPoint;

                        var xMax = original.x + primitive.length * 0.5f;
                        var yMax = original.y + primitive.height * 0.5f;
                        var zMax = original.z + primitive.width * 0.5f;

                        var xMin = original.x - primitive.length * 0.5f;
                        var yMin = original.y - primitive.height * 0.5f;
                        var zMin = original.z - primitive.width * 0.5f;

                        for (float x = xMin; x < xMax; x += xStep)
                        {
                            for (float y = yMin; y < yMax; y += yStep)
                            {
                                for (float z = zMin; z < zMax; z += zStep)
                                {
                                    var position = new Vector3(x, y, z);

                                    Gizmos.DrawWireSphere(position, 0.05f);
                                }
                            }
                        }
                        break;
                }
            }
            #endregion
        }

        public void UpdateValue(Vector3[] values)
        {
            for (int i = 0; i < mPointsList.Count; i++)
            {
                var item = mPointsList[i];
                item.position = values[i];
            }
        }

        public virtual Vector3 GetValue(int type, int inx)
        {
            return mSourceValueArray[inx];
        }

        void InitRenderType(RenderTypeEnum renderType)
        {
            switch (renderType)
            {
                case RenderTypeEnum.Point:

                    var tmp = GameObject.CreatePrimitive(PrimitiveType.Quad);
                    var quadMesh = Instantiate(tmp.GetComponent<MeshFilter>().sharedMesh);
                    Destroy(tmp);

                    foreach (var item in mPointsList)
                    {
                        var meshFilter = item.GetComponent<MeshFilter>();
                        if (meshFilter == null)
                            meshFilter = item.gameObject.AddComponent<MeshFilter>();

                        var meshRenderer = item.GetComponent<MeshRenderer>();
                        if (meshRenderer == null)
                            meshRenderer = item.gameObject.AddComponent<MeshRenderer>();

                        meshFilter.mesh = quadMesh;

                        if (rendererSettings.useSharedMaterial)
                            meshRenderer.sharedMaterial = rendererSettings.pointMaterial;
                        else
                            meshRenderer.material = rendererSettings.pointMaterial;
                    }

                    break;

                case RenderTypeEnum.PointAndLine:

                    InitRenderType(RenderTypeEnum.Point);
                    InitRenderType(RenderTypeEnum.Line);

                    break;
            }
        }

        void TryUpdateLineRenderer()
        {
            if (rendererSettings.renderType != RenderTypeEnum.Line
                && rendererSettings.renderType != RenderTypeEnum.PointAndLine)
                return;

            if (mLineRendererHub == null)
            {
                mLineRendererHub = new LineRendererHub();

                var lineRendererPairList = ConvertToLineRendererPair();

                mLineRendererHub.LineRendererWidth = rendererSettings.lineRendererWidth;

                mLineRendererHub.UseSharedMaterial = rendererSettings.useSharedMaterial;
                mLineRendererHub.Material = rendererSettings.lineMaterial;

                mLineRendererHub.Init(gameObject, lineRendererPairList.ToArray());
            }//Init line renderer.

            mLineRendererHub.Update();
        }

        void InitGeometry()
        {
            switch (geometrySettings.geometryType)
            {
                case GeometryTypeEnum.Primitive:

                    var primitive = geometrySettings.primitiveSettings;

                    var original = transform.position;
                    float xStep = primitive.length / (float)primitive.xPoint;
                    float yStep = primitive.height / (float)primitive.yPoint;
                    float zStep = primitive.width / (float)primitive.zPoint;
                    var xMax = original.x + primitive.length * 0.5f;
                    var yMax = original.y + primitive.height * 0.5f;
                    var zMax = original.z + primitive.width * 0.5f;

                    var xMin = original.x - primitive.length * 0.5f;
                    var yMin = original.y - primitive.height * 0.5f;
                    var zMin = original.z - primitive.width * 0.5f;

                    mPointsList.Clear();

                    var k = 0;
                    for (float x = xMin; x < xMax; x += xStep)
                    {
                        for (float y = yMin; y < yMax; y += yStep)
                        {
                            for (float z = zMin; z < zMax; z += zStep)
                            {
                                var newGameObject = new GameObject();
                                newGameObject.name = "k " + k;
                                newGameObject.transform.parent = transform;
                                newGameObject.transform.position = new Vector3(x, y, z);
                                newGameObject.transform.localScale = Vector3.one * geometrySettings.primitiveSettings.scale;

                                mPointsList.Add(newGameObject.transform);
                                k++;
                            }
                        }
                    }

                    break;
            }
        }

        List<LineRendererHub.Pair> ConvertToLineRendererPair()
        {
            var result = new List<LineRendererHub.Pair>();

            var primitive = geometrySettings.primitiveSettings;

            var original = transform.position;
            float xStep = primitive.length / (float)primitive.xPoint;
            float yStep = primitive.height / (float)primitive.yPoint;
            float zStep = primitive.width / (float)primitive.zPoint;

            var xMax = original.x + primitive.length * 0.5f;
            var yMax = original.y + primitive.height * 0.5f;
            var zMax = original.z + primitive.width * 0.5f;

            var xMin = original.x - primitive.length * 0.5f;
            var yMin = original.y - primitive.height * 0.5f;
            var zMin = original.z - primitive.width * 0.5f;

            var xIndex = 0;
            var yIndex = 0;
            var zIndex = 0;
            var k = 0;

            for (float x = xMin; x < xMax; x += xStep)
            {
                yIndex = 0;
                for (float y = yMin; y < yMax; y += yStep)
                {
                    zIndex = 0;
                    for (float z = zMin; z < zMax; z += zStep)
                    {
                        if (xIndex > 0)
                        {
                            //Debug.Log("k " + k + " k2: " + (k - primitive.xPoint * primitive.yPoint));
                            var a = mPointsList[k - primitive.zPoint * primitive.yPoint];
                            var b = mPointsList[k];
                            result.Add(new LineRendererHub.Pair() { A = a, B = b });
                        };

                        if (yIndex > 0)
                        {
                            var a = mPointsList[k - primitive.yPoint];
                            var b = mPointsList[k];
                            result.Add(new LineRendererHub.Pair() { A = a, B = b });
                        };

                        if (zIndex > 0)
                        {
                            var a = mPointsList[k - 1];
                            var b = mPointsList[k];
                            result.Add(new LineRendererHub.Pair() { A = a, B = b });
                        };

                        zIndex++;
                        k++;
                    }
                    yIndex++;
                }
                xIndex++;
            }

            return result;
        }
    }
}
